package com.google.android.apps.skipbot;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import android.content.Context;
import android.util.Log;

public class LoadableRobotConfig implements RobotConfig, Serializable {
  private static final long serialVersionUID = -2798441577253087009L;
  private static final String TAG = "LoadableRobotConfig";

  private final ArrayList<ServoConfig> servos;

  public LoadableRobotConfig() {
    servos = new ArrayList<ServoConfig>();
  }

  static public class ServoConfig implements Serializable {
    private static final long serialVersionUID = -8033751744571116709L;
    private int pinNumber;
    private int downAngle;
    private int upAngle;
    private int calibrationShift;
    private boolean flipped;

    public ServoConfig(int pinNumber, int downAngle, int upAngle, int calibrationShift,
        boolean flipped) {
      this.pinNumber = pinNumber;
      this.downAngle = downAngle;
      this.upAngle = upAngle;
      this.calibrationShift = calibrationShift;
      this.flipped = flipped;
    }

    public int getPinNumber() {
      return pinNumber;
    }

    public void setPinNumber(int pinNumber) {
      this.pinNumber = pinNumber;
    }

    public int getDownAngle() {
      return downAngle;
    }

    public void setDownAngle(int downAngle) {
      this.downAngle = downAngle;
    }

    public int getUpAngle() {
      return upAngle;
    }

    public void setUpAngle(int upAngle) {
      this.upAngle = upAngle;
    }

    public int getCalibrationShift() {
      return calibrationShift;
    }

    public void setCalibrationShift(int calibrationShift) {
      this.calibrationShift = calibrationShift;
    }

    public boolean isFlipped() {
      return flipped;
    }

    public void setFlipped(boolean flipped) {
      this.flipped = flipped;
    }

    public boolean isValid() {
      // TODO(dkhawk) these values are somewhat arbitrary
      if (Math.abs(downAngle - upAngle) < 10) {
        return false;
      }

      if (Math.abs(calibrationShift) > 90) {
        return false;
      }

      return true;
    }
  }

  /**
   * Loads a {@link LoadableRobotConfig} from local storage.
   * 
   * @param context the Android context object
   * @param configName the name of the configuration to load
   * @return a new {@link LoadableRobotConfig} object
   */
  static public LoadableRobotConfig load(Context context, String configName) {
    return (LoadableRobotConfig) Utilities.loadObject(context, configName);
  }

  /**
   * Saves this robot config to local storage. This should not be called from
   * the main thread.
   * 
   * @param context the Android context object
   * @param configName the name for this configuration. Will be overwritten if
   *        it exists.
   * @return
   */
  public boolean save(Context context, String configName) {
    return Utilities.saveObject(context, configName, this);
  }

  /**
   * Adds a new servo to the configuration.
   * 
   * @param pinNumber the pin number for the servo
   * @param downAngle the angle which represents down
   * @param upAngle the angle which represents up
   * @param calibrationShift the calibration shift to apply to movements
   * @param flipped if the servo direction is flipped
   */
  public void addServo(int pinNumber, int downAngle, int upAngle, int calibrationShift,
      boolean flipped) {
    servos.add(new ServoConfig(pinNumber, downAngle, upAngle, calibrationShift, flipped));
  }

  @Override
  public int getNumServos() {
    return servos.size();
  }

  @Override
  public int getPin(int servoIndex) {
    return servos.get(servoIndex).pinNumber;
  }

  void setPin(int servoIndex, int pinNumber) {
    servos.get(servoIndex).pinNumber = pinNumber;
  }

  @Override
  public int getDownAngle(int servoIndex) {
    return servos.get(servoIndex).downAngle;
  }

  void setDownAngle(int servoIndex, int downAngle) {
    servos.get(servoIndex).downAngle = downAngle;
  }

  @Override
  public int getUpAngle(int servoIndex) {
    return servos.get(servoIndex).upAngle;
  }

  void setUpAngle(int servoIndex, int upAngle) {
    servos.get(servoIndex).upAngle = upAngle;
  }

  @Override
  public int getCalibrationShift(int servoIndex) {
    return servos.get(servoIndex).calibrationShift;
  }

  void setCalibrationShift(int servoIndex, int calibrationShift) {
    servos.get(servoIndex).calibrationShift = calibrationShift;
  }

  public boolean isValid() {
    Set<Integer> pins = new HashSet<Integer>(servos.size());
    for (ServoConfig servo : servos) {
      if (pins.contains(servo.pinNumber)) {
        Log.e(TAG, "Duplicated pin found: " + servo.pinNumber);
        return false;
      }

      if (!servo.isValid()) {
        Log.e(TAG, "Invalid servo config: " + servo);
        return false;
      }

      pins.add(servo.pinNumber);
    }

    return true;
  }

  @Override
  public boolean getDirectionFlipped(int servoIndex) {
    // TODO Auto-generated method stub
    return servos.get(servoIndex).flipped;
  }

  void setFlipped(int servoIndex, boolean flipped) {
    servos.get(servoIndex).flipped = flipped;
  }
}
